package com.leyou.search.service.impl;

import static com.leyou.search.constants.SearchConstants.*;

import com.leyou.clients.ItemClient;
import com.leyou.common.exception.LyException;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ItemClient itemClient;

    /**
     * 自动补全
     *
     * @param key 用户输入的搜索条件
     * @return 自动补全的信息
     */
    @Override
    public Mono<List<String>> getSuggest(String key) {
        if (StringUtils.isBlank(key)) {
            throw new LyException(400, "请求参数不能为空");
        }
        return goodsRepository.suggestBySingleField(SUGGESTION_FIELD, key);
    }

    /**
     * 根据搜索条件，查询到商品集合
     *
     * @param searchRequest 搜索条件
     * @return 分页显示的商品集合
     */
    @Override
    public Mono<PageInfo<Goods>> listData(SearchRequest searchRequest) {

        // 创建查询条件对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        /**
         * 1. 查询条件
         */
        QueryBuilder queryBuilder = getQueryBuilder(searchRequest);
        sourceBuilder.query(queryBuilder);

        /**
         * 2. 分页条件
         */
        Integer page = searchRequest.getPage() == null ? 1 : searchRequest.getPage();
        Integer size = searchRequest.getRows();
        int from = (page - 1) * size;
        sourceBuilder.from(from);
        sourceBuilder.size(size);

        /**
         * 3. 排序条件
         */
        if (!StringUtils.isBlank(searchRequest.getSortBy())) {
            sourceBuilder.sort(
                    searchRequest.getSortBy(),
                    searchRequest.getDesc() ? SortOrder.DESC : SortOrder.ASC);
        }

        /**
         * 4. 高亮条件
         */
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags(DEFAULT_PRE_TAG);
        highlightBuilder.postTags(DEFAULT_POST_TAG);
        highlightBuilder.field(DEFAULT_SEARCH_FIELD);
        sourceBuilder.highlighter(highlightBuilder);


        return goodsRepository.queryBySourceBuilderForPageHighlight(sourceBuilder);
    }

    /**
     * 获取查询条件
     *
     * @param searchRequest 查询条件
     * @return 查询对象
     */
    private QueryBuilder getQueryBuilder(SearchRequest searchRequest) {
        // 1. 判断当前查询条件是否为空
        if (StringUtils.isBlank(searchRequest.getKey())) {
            throw new LyException(400, "查询字段不能为空");
        }
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        boolQueryBuilder.must(QueryBuilders.matchQuery(DEFAULT_SEARCH_FIELD, searchRequest.getKey()).operator(Operator.AND));

        // 获取过滤条件
        Map<String, String> filters = searchRequest.getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            // 有过滤条件
            filters.entrySet().forEach(entry -> {
                String key = entry.getKey();
                String value = entry.getValue();

                // 如果是分类，按照分类id
                if ("分类".equals(key)) {
                    key = "categoryId";
                    boolQueryBuilder.filter(QueryBuilders.termQuery(key, value));
                } else if ("品牌".equals(key)) {
                    // 如果是品牌，按照品牌id
                    key = "brandId";
                    boolQueryBuilder.filter(QueryBuilders.termQuery(key, value));
                } else {

                    boolQueryBuilder.filter(
                            QueryBuilders.nestedQuery("specs",
                                    QueryBuilders.boolQuery()
                                            .must(QueryBuilders.termQuery("specs.name", key))
                                            .must(QueryBuilders.termQuery("specs.value", value))
                                    , ScoreMode.None));

                    //QueryBuilders.boolQuery().must(
//                            QueryBuilders.termQuery("specs.name", value))
                    //QueryBuilders.termQuery("specs.name", key), QueryBuilders.termQuery("specs.value", value)
                }


            });
        }

        // 添加查询条件
        return boolQueryBuilder;
    }

    /**
     * 根据用户输入的条件，返回过滤条件
     *
     * @param searchRequest 用户输入的条件
     * @return 过滤条件
     */
    @Override
    public Mono<Map<String, List<? extends Object>>> listFilter(SearchRequest searchRequest) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        /**
         * 添加查询条件查询
         */
        sourceBuilder.query(getQueryBuilder(searchRequest));

        /**
         * 聚合
         */
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(20));
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryAgg").field("categoryId").size(20));

        sourceBuilder.aggregation(
                AggregationBuilders.nested("specAgg", "specs")
                        .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name").size(20)
                                .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value").size(20))));


        Mono<Aggregations> aggregationsMono = goodsRepository.aggregationBySourceBuilder(sourceBuilder);

        return aggregationsMono.map(aggregations -> {
            HashMap<String, List<? extends Object>> resultMap = new LinkedHashMap<>();

            Terms brandAgg = aggregations.get("brandAgg");

            List<Long> brandIds = brandAgg
                    .getBuckets()
                    .stream()
                    .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                    .map(Number::longValue)
                    .collect(Collectors.toList());


//            ArrayList<Long> brandIds = new ArrayList<>();
//            List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
//            for (Terms.Bucket bucket : buckets) {
//                brandIds.add(bucket.getKeyAsNumber().longValue());
//            }

            if (!CollectionUtils.isEmpty(brandIds)) {
                resultMap.put("品牌", itemClient.queryBrandByIds(brandIds));
            }


            Terms categoryAgg = aggregations.get("categoryAgg");

            List<Long> categoryIds = categoryAgg.getBuckets()
                    .stream()
                    .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                    .map(Number::longValue)
                    .collect(Collectors.toList());

            if (!CollectionUtils.isEmpty(categoryIds)) {
                resultMap.put("分类", itemClient.queryCategoryByIds(categoryIds));
            }

            Nested specAgg = aggregations.get("specAgg");
            Terms nameAgg = specAgg.getAggregations().get("nameAgg");
            nameAgg.getBuckets().forEach(bucket -> {
                String key = ((Terms.Bucket) bucket).getKeyAsString();

                Terms valueAgg = ((Terms.Bucket) bucket).getAggregations().get("valueAgg");

                List<String> valueList =
                        valueAgg.getBuckets()
                                .stream()
                                .map(valueBucket -> ((Terms.Bucket) valueBucket).getKeyAsString())
                                .collect(Collectors.toList());
                resultMap.put(key, valueList);
            });

            return resultMap;
        });

    }

}
